iThome 鐵人賽 30天今天要來打造影片標記系統的精隨之一,用YouTube Iframe API來建立一個播放器。或許有人會有疑問,為甚麼不用Iframe就好了呢?
因為為了能夠隨時抓取YouTube影片的撥放時間、打字編輯標記時也能夠控制影片撥放暫停等,所以使用YouTube Iframe API輔助來打造高控制度的撥放器。
這裡大致介紹一下功能,主要介紹一些function與event listener。
funtion:
| function | 說明 | 
|---|---|
| playVideo | 播放 | 
| pauseVideo | 暫停 | 
| stopVideo | 停止 | 
| seekTo | 跳到影片位置 (秒) | 
| mute | 靜音 | 
| unMute | 解除靜音 | 
| isMuted | 查看現在是否靜音 | 
| setVolume | 設定音量 | 
| getVolume | 取得目前音量 | 
| getCurrentTime | 取得現在影片撥放時間點 | 
| getDuration | 取得影片長度 | 
事件:
| 事件名稱 | 說明 | 
|---|---|
| onReady | 當影片準備好時 | 
| onStateChange | 當影片狀態改變時 (-1 尚未開始、0 結束、1 正在播放、2 暫停、3 緩衝、5 影片提示) | 
| onPlaybackQualityChange | 當影片畫質改變時 | 
| onPlaybackRateChange | 當影片播放速度改變時 | 
| onError | 當影片發生錯誤時 | 
| onApiChange | 當YouTube API改變時 | 
以上是常用到的function與event,如果想查看更詳細的API的話可以到官方API文件去查詢喔!
第一個步驟先定義要放置player的容器 (id可以自己取名):
<div id="player"></div>
再來就是要先把Iframe的API載入進來 (當api準備好時會執行loadVideo):
const tag = document.createElement('script');
tag.src = 'https://www.youtube.com/iframe_api';
window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
const firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
定義loadVideo來撥放影片:
loadVideo = () => { // the Player object is created uniquely based on the id in props
const { videoid } = this.props;
player = new YT.Player(playerid, {
    videoId: videoid,
    playerVars: {
        'playsinline': 1
      },
    events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange,
        'onPlaybackQualityChange': onPlaybackQualityChange,
    },
})
介紹使用方法後就是來把這些步驟包成一個component來處理啦~
component第一次執行載入js:
useEffect(() => {
  loadYTApi()
}, [])
const loadYTApi = () => {
  if (!window.YT) { // If not, load the script asynchronously
    const tag = document.createElement('script');
    tag.src = 'https://www.youtube.com/iframe_api';
    window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
    const firstScriptTag = document.getElementsByTagName('script')[0];
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
  } else { // If script is already there, load the video directly
    this.loadVideo();
  }
}
讓影片 (v) 與開始時間 (t) 由props傳入,並請使用者傳入設定player與其他監聽事件的function:
const { v, t, setPlayer, onPlayerReady, onPlayerStateChange, onPlaybackQualityChange, player, playerid } = props
const loadVideo = () => { // the Player object is created uniquely based on the id in props
    setPlayer(new window.YT.Player(playerid, {
        videoId: v,
        playerVars: {
            'start': parseFloat(t)
        },
        events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange,
            'onPlaybackQualityChange': onPlaybackQualityChange
        },
    }))
};
當傳入的v與t變動時,更新影片或時間點:
const { v, t, setPlayer } = props
// 當影片id變更時,載入新的影片
useEffect(() => {
    player &&
        player.loadVideoById({
          videoId: v,
          startSeconds: parseFloat(t)
        })
}, [v])
// 開始時間改變時,跳轉到該時間
useEffect(() => {
    player &&
        player.seekTo(t)
}, [t])
把上面講述到的融合後,得到的component:
import React, { useEffect } from 'react'
export default function YouTubeIframe(props) {
  const { v, t, setPlayer, onPlayerReady, onPlayerStateChange, onPlaybackQualityChange, playerid, player } = props
  useEffect(() => {
    loadYTApi()
  }, [])
  // 當影片id變更時,載入新的影片
  useEffect(() => {
    !!player &&
      player.loadVideoById({
        videoId: v,
        startSeconds: parseFloat(t)
      })
  }, [v])
  // 開始時間改變時,跳轉到該時間
  useEffect(() => {
    !!player &&
      player.seekTo(t)
  }, [t])
  const loadYTApi = () => {
    if (!window.YT) { // If not, load the script asynchronously
      const tag = document.createElement('script');
      tag.src = 'https://www.youtube.com/iframe_api';
      window.onYouTubeIframeAPIReady = loadVideo; // onYouTubeIframeAPIReady will load the video after the script is loaded
      const firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
    } else { // If script is already there, load the video directly
      loadVideo();
    }
  }
  const loadVideo = () => { // the Player object is created uniquely based on the id in props
    setPlayer(new window.YT.Player(playerid, {
      videoId: v,
      playerVars: {
        'start': parseFloat(t)
      },
      events: {
        'onReady': onPlayerReady,
        'onStateChange': onPlayerStateChange,
        'onPlaybackQualityChange': onPlaybackQualityChange
      },
    }))
  };
  return (
    <></>
  )
}
明天就來試用看看並調整今天做的component吧!
看完今天Google的發表會,雖然又有些新的技術與新出來的手錶,雖然手錶真的很美,但感覺這次發表會沒有一種吸引人想買的衝動,硬體上面又感覺力不從心,只好等等看明年吧... (Pixel 6的苦主在這
附上專案:2022-iThomeIronman
對資安或Mapbox有興趣的話也可以觀看我們團隊的鐵人發文喔~